package rbb.itm5.bwinf.roboter.Model;

import java.awt.Dimension;
import java.awt.Point;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import rbb.itm5.bwinf.roboter.Control.ControlerEvents;
import rbb.itm5.bwinf.roboter.Control.Roboter;

/**
 * Diese Klasse erstellt eine Raum, in dem sich der Roboter bewegen kann. Alle
 * Hindernisse müssen über diese Klasse erstellt werden, damit sie erkannt
 * werden.
 *
 * @author Dominik Jakielski
 * @version 1 *
 */
public class Room {

    private int width, height, RoboterStartX, RoboterStartY;
    private ArrayList<Obstacle> obstacles = new ArrayList<Obstacle>();
    private Door door = null;
    private Roboter roboter;
    public static int GridSize = 20;
    private boolean editable;
    ControlerEvents event;

    /**
     * Der Konstruktor baut ein Beispiel Raum auf und erzeugt den Roboter.
     *
     * @param event Events
     */
    public Room(ControlerEvents event) {

        //Testraum mit Hindernissen erstellen
        door = new Door(1, 1, 4, 0);
        addObstacle(7, 1, 2, 7);
        addObstacle(9, 2, 8, 1);
        addObstacle(5, 6, 2, 2);
        addObstacle(6, 9, 4, 2);
        addObstacle(4, 15, 14, 2);
        addObstacle(18, 1, 1, 4);
        addObstacle(10, 4, 8, 1);
        RoboterStartX = 4;
        RoboterStartY = 4;

        this.event = event;
        //Neuen Roboter erstellen
        roboter = new Roboter(this, event);
    }

    /**
     * Erstellt ein neues Hindernis im Raum, sofern es gültig ist. Nach der
     * Erstellung wird dieses im Hindernisspeicher des Raumes gesichert.
     *
     * @param x Horizontale Position im Raum
     * @param y Vertikale Position im Raum
     * @param width Breite des Hindernis
     * @param height Höhe des Hindernis
     * @return Erfolgsflag, ob das Hindernis erzeugt wurde.
     */
    private boolean addObstacle(int x, int y, int width, int height) {
        if (isValidObstacle(x, y, width, height)) {
            return obstacles.add(new Obstacle(x, y, width, height));
        }
        return false;
    }

    /**
     * Die Methode prüft ob die Hindernis-Werte gültig sind und nicht mit
     * bereits vorhandenen kolidiert.
     *
     * @param x Horizontale Position im Raum
     * @param y Vertikale Position im Raum
     * @param width Breite des Hindernis
     * @param height Höhe des Hindernis
     * @return Ist ein gültiges Hindernis?
     */
    private boolean isValidObstacle(int x, int y, int width, int height) {
        boolean re = true;
        for (Obstacle obstacle : obstacles) {
            Point p = obstacle.getPostion();
            Dimension s = obstacle.getSize();
            int diffX = Math.abs(p.x - x), diffY = Math.abs(p.y - y);
            if ((diffX < s.width && diffX < width && p.x + s.width < x) && (diffY < s.height || diffY < height && p.y + s.height < y)) {
                re = false;
                break;
            }
        }
        return re;
    }

    /**
     * Gibt die Größe des Raums in einem Dimension-Objekt zurück.
     *
     * @see Dimension
     * @return Dimension
     */
    public Dimension getSize() {
        return new Dimension(width, height);
    }

    /**
     * Gibt alle Hindernisse des Hindernisspeichers zurück;
     *
     * @return Alle Hindernisse
     */
    public ArrayList<Obstacle> getObstacles() {
        return obstacles;
    }

    /**
     * Gibt ein Hindernis zurück.
     *
     * @param i Index-ID des Hindernis
     * @return Hindernis
     */
    public Obstacle getObstacle(int i) {
        return obstacles.size() > i ? obstacles.get(i) : null;
    }

    /**
     * Diese Methode erstellt einen Raum anhand einer Datei. <br> <b>Aufbau der
     * Datei:</b><br>
     * <code>
     * Zeile 1: Breite Länge [des Raumes, in Rasterquadraten, einschließlich Wände]
     * Zeile 2: x y [Standort des Roboters]
     * Zeile 3: x1 y1 x2 y2 [Ausgang: Lücke in einer Wand; x1=x2 oder y1=y2]
     * Zeile 4: N [Anzahl der Hindernisse/Rechtecke]
     * Zeilen 5 bis N+4 jeweils: x1 y1 x2 y2 [ein Hindernis/Rechteck]
     * </code>
     *
     * @param FileName Dateipfad der Raum-Datei.
     * @return Erfolgsflag, ob der Raum geladen wurde.
     */
    public boolean loadRoom(String FileName) {
        boolean re = true;
        Scanner sc;
        obstacles.clear();
        try {
            sc = new Scanner(new File(FileName));
            int ObstacleCount = 0;
            for (int i = 0; sc.hasNextLine(); i++) {
                String line = sc.nextLine();
                if (line.equals("\n") || line.isEmpty()) {
                    continue;
                }
                String[] lineValues = line.split(" ");
                if (lineValues.length == 0) {
                    lineValues = new String[]{line};
                }
                //Einzelne Zeilen durchgehen
                switch (i) {
                    case 0:
                        width = Integer.parseInt(lineValues[0]);
                        height = Integer.parseInt(lineValues[1]);
                        break;
                    case 1:
                        RoboterStartX = Integer.parseInt(lineValues[0]);
                        RoboterStartY = Integer.parseInt(lineValues[1]);
                        break;
                    case 2:
                        door = new Door(Integer.parseInt(lineValues[0]), Integer.parseInt(lineValues[1]), Integer.parseInt(lineValues[2]) - Integer.parseInt(lineValues[0]), Integer.parseInt(lineValues[3]) - Integer.parseInt(lineValues[1]));
                        break;
                    case 3:
                        ObstacleCount = Integer.parseInt(lineValues[0]);
                        break;
                    default:
                        addObstacle(Integer.parseInt(lineValues[0]), Integer.parseInt(lineValues[1]), Integer.parseInt(lineValues[2]) - Integer.parseInt(lineValues[0]), Integer.parseInt(lineValues[3]) - Integer.parseInt(lineValues[1]));
                        break;
                }
            }
            //Rastergröße anpassen
            if (width >= 100 || height >= 100) {
                GridSize = 10;
            } else if (width >= 50 || height >= 50) {
                GridSize = 15;
            }
            event.setViewSize(height * GridSize, width * GridSize);
            if (ObstacleCount != obstacles.size()) {
                re = false;
            }
        } catch (FileNotFoundException e) {
            re = false;
        }

        return re;
    }

    /**
     * Speichert den aktuellen Raum in einer Raum-Datei.
     *
     * @param FileName Dateipfad der Raum-Datei.
     */
    public void saveRoom(String FileName) {
        try {
            File f = new File(FileName);
            FileWriter fw = new FileWriter(f);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.append(width + " " + height + "\n");
            bw.append(RoboterStartX + " " + RoboterStartY + "\n");
            if (door.getOrientation() == Door.HORIZONTAL) {
                bw.append((int) door.getPostion().getX() + " " + (int) door.getPostion().getY() + " " + (int) (door.getPostion().getX() + door.getSize()) + " " + (int) door.getPostion().getY() + "\n");
            } else {
                bw.append((int) door.getPostion().getX() + " " + (int) door.getPostion().getY() + " " + (int) door.getPostion().getX() + " " + (int) (door.getPostion().getY() + door.getSize()) + "\n");
            }
            bw.append(obstacles.size() + "\n");
            //Hindernisse hinzufügen
            for (Obstacle ob : obstacles) {
                bw.append((int) ob.getPostion().getX() + " " + (int) ob.getPostion().getY() + " " + (int) (ob.getPostion().getX() + ob.getSize().width) + " " + (int) (ob.getPostion().getY() + ob.getSize().height) + "\n");
            }
            bw.flush();
            bw.close();
            fw.close();
        } catch (IOException ex) {
            Logger.getLogger(Room.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Gibt das Tür-Model zurück.
     *
     * @see Door
     * @return Tür-Objekt
     */
    public Door getDoor() {
        return door;
    }

    /**
     * Gibt den Roboter zurück.
     *
     * @see Roboter
     * @return Roboter
     */
    public Roboter getRoboter() {
        return roboter;
    }

    /**
     * Gibt die horizontale Startposition des Roboters zurück.
     *
     * @return horizontale Startposition
     */
    public int getRoboterStartX() {
        return RoboterStartX;
    }

    /**
     * Gibt die vertikale Startposition des Roboters zurück.
     *
     * @return vertikale Startposition
     */
    public int getRoboterStartY() {
        return RoboterStartY;
    }

    /**
     * Gibt die Rastergröße zurück.
     *
     * @return Rastergröße
     */
    public int getGridSize() {
        return GridSize;
    }

    /**
     * Setter für die horizontale Startposition <br>Eine Bearbeitung kann erst
     * erfolgen, wenn vorher das Objekt mit setEditable(true) als Editierbar
     * gekennzeichnet wurde.
     *
     * @param RoboterStartX horizontale Startposition
     */
    public void setRoboterStartX(int RoboterStartX) {
        if (editable && RoboterStartX >= 0 && RoboterStartX <= width) {
            this.RoboterStartX = RoboterStartX;
            event.repaintView();
        }
    }

    /**
     * Setter für die vertikale Startposition <br>Eine Bearbeitung kann erst
     * erfolgen, wenn vorher das Objekt mit setEditable(true) als Editierbar
     * gekennzeichnet wurde.
     *
     * @param RoboterStartY horizontale Startposition
     */
    public void setRoboterStartY(int RoboterStartY) {
        if (editable && RoboterStartY >= 0 && RoboterStartY <= height) {
            this.RoboterStartY = RoboterStartY;
            event.repaintView();
        }
    }

    /**
     * Setter für den Breite-Wert <br>Eine Bearbeitung kann erst erfolgen, wenn
     * vorher das Objekt mit setEditable(true) als Editierbar gekennzeichnet
     * wurde.
     *
     * @param width Neuer Breite-Wert
     */
    public void setWidth(int width) {
        if (editable && width >= 0 && width <= 150) {
            this.width = width;
            event.repaintView();
        }
    }

    /**
     * Setter für den Höhe-Wert <br>Eine Bearbeitung kann erst erfolgen, wenn
     * vorher das Objekt mit setEditable(true) als Editierbar gekennzeichnet
     * wurde.
     *
     * @param height Neuer Höhe-Wert
     */
    public void setHeight(int height) {
        if (editable && height >= 0 && height <= 150) {
            this.height = height;
            event.repaintView();
        }
    }

    /**
     * De- Aktiviert den Bearbeitungsmodus muss die Eigenschaften eines Raum zu
     * bearbeiten.
     *
     * @param editable Status True= Eingeschaltet, False=Ausgeschaltet
     */
    public void setEditable(boolean editable) {
        this.editable = editable;
    }
}
